jest mock
参考
気づいたこと
[基本] jestにおけるモジュールモックでは、モジュールをモック化した後にimportするってのが基本。
つまり、モック化する前にimportされたモジュールは、実体が使われてる。
例えば、moduleBを利用するmoduleAがあるとして、moduleBをモック化する前にmoduleAをimportしちゃうと...
その後、moduleBをモック化したとしても、moduleA内では反映されないので注意。
jest.mockはファイルの最初に実行するのが原則
import文群のすぐ次に実行するのが最適。
jestは内部的に、ファイル内の最上位階層に記述されてるjest.mock文をimport文よりも前に移動してファイル実行している。
最初疑問だった...onigiri.w2.icon
「なぜimportした後に、モック化してるのに、それが上手くいってるんだ?」と。
それは、この内部的な動きが原因だったのね。
注意.icon ちなみに、そういう性質から、関数内(describe, testなど)でjest.mockを実行してしまうと、その実行後に関連モジュールをrequire or importする必要があるので注意。
モックの挙動に違和感がある際は、もしかしたら「resetModules」「resetMocks」がfalseにされてる可能性もある
明示的にresetModules, resetMocksなどしてないのに、テスト間でモック内容がリセットされてそうなら、もしかしたら設定で自動的にtrueになってる可能性あり。
なので、一度、resetModules, resetMocksの設定値をfalseにしてみると何か変わるかも。
モジュールが関連する場合は、「モック化 -> import or require -> モック内部処理定義 -> 利用 -> (モックリセット or クリア)」のサイクルが基本なので覚えておくように
モジュールが関連するモックの場合は、上記サイクルが基本であることを認識しておくこと。
とにかく、モジュールはモック化した後にimportしないとモックにならないので注意。
importの仕方は、崩れてようが、なかろうがどっちでもいい。どっちにしろモック化されてる。
code: sample1.test.js
import { random } from 'moduleA';
import { calculate } from 'moduleB';
// この時点で、moduleAはモック化されており、さらにmoduleB内で利用するmoduleAもモック化されてる。
jest.mock('moduleA');
test('sample', () => {
randam.mockimplementation(() => 2);
const actual = calculate();
expect(actual).toBe(2);
expect(random).toHaveBeenCalledTimes(1);
}
code: sample2.test.js
import * as moduleA from 'moduleA';
import { calculate } from 'moduleB';
// この時点で、moduleAはモック化されており、さらにmoduleB内で利用するmoduleAもモック化されてる。
jest.mock('moduleA');
test('sample', () => {
moduleA.randam.mockimplementation(() => 2);
const actual = calculate();
expect(actual).toBe(2);
expect(moduleA.random).toHaveBeenCalledTimes(1);
}
jest.mockでモック化したモジュールの定数に対して直接代入することはできないので注意
モジュール全体をモック化したと思って、モジュール内の定数に直接代入しようとすると失敗する。
「読み取り専用ですよ」と言われて怒られる。
まあ、これは無理なんです。定数なんで。
じゃあどうすればいいん?
jest.mockでモック化して、ファクトリを定義する際に、__esModule: trueと設定するといい。
これやると、定数も一緒に変更可能になる。
jest.spyOnはオブジェクトに対して、直接モックの手を加えるやつ
関数はモック化できないので注意ね。オブジェクトの中身をモック化する感じ。
spyOnを使うにあたって、jest.mockは必ずしも必要ではない。
code: sample.js
const obj = {
random: () => Math.random();
}
code: sampleA.js
import {obj} from 'sample.js';
const calculate = () => {
return obj.random();
}
code: sample.test.js
import {calculate} from './sampleA';
import {obj} from './sample';
test('test desu', () => {
const mockRandom = jest.spyOn(obj, 'random');
mockRandom.mockImplementation(() => 2);
calculate();
expect(obj.random).toHaveBeenCalledTimes(1);
});
わざわざモジュールをimportし直さなくてもいい。対象オブジェクトの中身をモック化できる。
jest.spyOnは、モック化の後にモジュールをimportするなんていう原則に従わなくていい
上記で説明済み。
というか、jest.mockはモジュールを模した別のモジュールモックというものを作って利用してる。
ので、本物のモジュールには手を加えてない。
対して、jest.spyOnは本物のモジュールの中にあるオブジェクトに手を加えてるので注意。
restore, reset, clearの違いをガッツリ理解しておこう
モックの状態を元に戻す系の関数としてclear系、reset系、restore系の3つある。
clear系(mockFn.mockClear(), jest.clearAllMocks())
対象モック、もしくは全モックのmock.calls, mock.instances, mock.contexts, mock.resultsをクリアする。
reset系(mockFn.mockReset(), jest.resetAllMocks())
clear系の内容に加えて、モックの実装もクリアする。
restore系(mockFn.mockRestore(), jest.restoreAllMocks())
jest.spyOnで作られてモックを解除する。
jest.resetModulesってなんな?
全てのrequireされたモジュールをリセットするらしい。
importしたモジュールがリセットされるのかは不明。
というかリセットて具体的に何するんだろう。
resetModulesしたが、結局、モック化されたモジュール自体はそのままっぽい。
中身の実装が元に戻るだけ説。
まだ、解像度はしっかり上がってないかもだわ。
jest.mockで一部のメンバーだけモック化したい場合